/**
 * Project: Utilities <br>
 * Package: org.leumasjaffe.collections.map
 */
package org.leumasjaffe.collections.map;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
 * Class org.leumasjaffe.collections.map..HashMultiMap in file HashMultiMap.java <br>
 * 
 * @author leumasjaffe
 * @version Feb 1, 2014
 * Created on: Feb 1, 2014
 * 
 */
@NonNullByDefault
public class DelegateMultiMap<K, V> extends AbstractMap<K, V> implements MultiMap<K, V> {
	private final Map<K, Collection<V>> internalMap;
	
	protected class MultiEntrySet extends AbstractSet<Entry<K, V>> {
		@SuppressWarnings("null")
		protected final Set<Entry<K, Collection<V>>> internalEntrySet = 
				DelegateMultiMap.this.internalMap.entrySet();
		
		/* (non-Javadoc)
		 * @see java.util.AbstractCollection#iterator()
		 */
		@SuppressWarnings("null")
		@Override
		public Iterator<Entry<K, V>> iterator() {
			return new MultiEntryIterator(this.internalEntrySet.iterator());
		}

		/* (non-Javadoc)
		 * @see java.util.AbstractCollection#size()
		 */
		@Override
		public int size() {
			return DelegateMultiMap.this.size();
		}
		
	}
	
	protected class MultiEntry extends SimpleEntry<K, V> {
		
		/**
		 * 
		 */
		private static final long serialVersionUID = 6380708665707495579L;
		protected final int location;

		/**
		 * @param key
		 * @param value
		 * @param location
		 */
		public MultiEntry(@Nullable K key, @Nullable V value, int location) {
			super(key, value);
			this.location = location;
		}

		/* (non-Javadoc)
		 * @see java.util.Map.Entry#setValue(java.lang.Object)
		 */
		@Override
		public @Nullable V setValue(@Nullable V value) {
			final Collection<V> col = DelegateMultiMap.this.internalMap.get(getKey());
			if (col instanceof List) {
				final V oldValue = super.setValue(value);
				((List<V>) col).set(this.location, value);
				return oldValue;
			} else {
				throw new UnsupportedOperationException();
			}
		}
		
	}
	
	protected class MultiEntryIterator implements Iterator<Entry<K, V>> {
		
		protected Iterator<Entry<K, Collection<V>>> internalIterator;
		protected @Nullable K nextKey = null;
		@SuppressWarnings("null")
		protected Iterator<V> nextIt = Collections.emptyIterator();
		protected int index = 0;
		
		MultiEntryIterator(Iterator<Entry<K, Collection<V>>> internalIterator) {
			this.internalIterator = internalIterator;
			advance();
		}

		/**
		 * 
		 */
		@SuppressWarnings("null")
		private void advance() {
			while (!this.nextIt.hasNext() && this.internalIterator.hasNext()) {
				final Entry<K, Collection<V>> entry = this.internalIterator.next();
				this.nextKey = entry.getKey();
				this.index = 0;
				if (entry.getValue() != null) {
					this.nextIt = entry.getValue().iterator();
				} else {
					this.nextIt = Collections.emptyIterator();
				}
			}
		}

		/* (non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		@Override
		public boolean hasNext() {
			return this.nextIt.hasNext();
		}

		/* (non-Javadoc)
		 * @see java.util.Iterator#next()
		 */
		@Override
		public Entry<K, V> next() {
			if (!hasNext()) {
				throw new NoSuchElementException();
			}
			Entry<K, V> entry = new MultiEntry(this.nextKey, this.nextIt.next(), this.index++);
			if (!hasNext()) {
				advance();
			}
			return entry;
		}

		/* (non-Javadoc)
		 * @see java.util.Iterator#remove()
		 */
		@Override
		public void remove() {
			this.nextIt.remove();
		}
		
	}
	
	private int count = 0;
	@SuppressWarnings("null")
	protected transient Set<Entry<K, V>> entrySet = null;
	
	@SuppressWarnings("unchecked")
	protected DelegateMultiMap(Map<K, ? extends Collection<V>> internalMap) {
		this.internalMap = (Map<K, Collection<V>>) internalMap;
	}
	
	/* (non-Javadoc)
	 * @see java.util.AbstractMap#size()
	 */
	@Override
	public int size() {
		return this.count;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#containsKey(java.lang.Object)
	 */
	@Override
	public boolean containsKey(@Nullable Object key) {
		return this.internalMap.containsKey(key);
	}

	/* (non-Javadoc)
	 * @see java.util.Map#get(java.lang.Object)
	 */
	@Override
	public @Nullable V get(@Nullable Object key) {
		@SuppressWarnings("unchecked")
		final Collection<V> data = internalGet((K) key);
		return data == null || data.isEmpty() ? null : data.iterator().next();
	}
	
	/* (non-Javadoc)
	 * @see java.util.MultiMap#get(java.lang.Object)
	 */
	@SuppressWarnings("null")
	@Override
	public Collection<V> getAll(@Nullable K key) {
		final Collection<V> data = internalGet(key);
		return (Collection<V>) (data == null ? Collections.emptyList() : data);
	}

	/* (non-Javadoc)
	 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
	 */
	@Override
	public @Nullable V put(@Nullable K key, @Nullable V value) {
		final Collection<V> data = internalInit(key);
		data.add(value);
		++this.count;
		return null;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#remove(java.lang.Object)
	 */
	@Override
	public @Nullable V remove(@Nullable Object key) {
		@SuppressWarnings("unchecked")
		final Collection<V> data = internalGet((K) key);
		V removed = null;
		if (data != null && !data.isEmpty()) {
			final Iterator<V> it = data.iterator();
			removed = it.next();
			it.remove();
			--this.count;
			if (data.isEmpty()) {
				this.internalMap.remove(key);
			}
		}
		return removed;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#entrySet()
	 */
	@Override
	public Set<Entry<K, V>> entrySet() {
		return this.entrySet == null ? this.entrySet = new MultiEntrySet() : this.entrySet;
	}
	
	/* (non-Javadoc)
	 * @see java.util.AbstractMap#keySet()
	 */
	@Override
	@SuppressWarnings("null")
	public Set<K> keySet() {
		return this.internalMap.keySet();
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.collections.map.MultiMap#putAll(java.lang.Object, java.util.Collection)
	 */
	@Override
	public void putAll(@Nullable K key, @Nullable Collection<? extends V> col) {
		internalInit(key).addAll(col);
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.collections.map.MultiMap#putAll(org.leumasjaffe.collections.map.MultiMap)
	 */
	@Override
	public <C extends K> void putAll(@Nullable MultiMap<C, ? extends V> m) {
		if (m instanceof DelegateMultiMap) {
			for (C key : m.keySet()) {
				internalInit(key).addAll(((DelegateMultiMap<C, ? extends V>) m).internalGet(key));
			}
		} else if (m != null) {
			for (C key : m.keySet()) {
				Collection<V> list = internalInit(key);
				final Iterator<? extends V> it = m.equalRange(key);
				while (it.hasNext()) {
					list.add(it.next());
				}
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.collections.map.MultiMap#remove(java.lang.Object, int)
	 */
	@Override
	public void remove(@Nullable K key, int count) {
		for (int i = 0; i < count; ++i) {
			remove(key);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.collections.map.MultiMap#removeAll(java.lang.Object)
	 */
	@Override
	public int removeAll(@Nullable K key) {
		final int removed = count(key);
		this.internalMap.remove(key);
		return removed;
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.collections.map.MultiMap#count(java.lang.Object)
	 */
	@Override
	public int count(@Nullable K key) {
		final @Nullable Collection<V> data = internalGet(key);
		return data == null ? 0 : data.size();
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.collections.map.MultiMap#equalRange(java.lang.Object)
	 */
	@SuppressWarnings("null")
	@Override
	public Iterator<V> equalRange(@Nullable K key) {
		final @Nullable Collection<V> data = internalGet(key);
		return (Iterator<V>) (data == null ? Collections.emptyIterator() : data.iterator());
	}
	
	/**
	 * @param key
	 * @return
	 */
	protected Collection<V> internalInit(@Nullable K key) {
		@Nullable Collection<V> data = internalGet(key);
		if (data == null) {
			data = newTypeInstance();
			this.internalMap.put(key, data);
		}
		return data;
	}

	/**
	 * @return
	 */
	protected Collection<V> newTypeInstance() {
		return new LinkedList<V>();
	}

	/**
	 * @param key
	 * @return
	 */
	protected @Nullable Collection<V> internalGet(@Nullable K key) {
		return this.internalMap.get(key);
	}
	
}
